﻿//-----------------------------------------------------------------------------
// (c) Terry Spitz, 2012
//-----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;

// This class handles the interactions with .Net Windows forms
class BrotWindowsForm : System.Windows.Forms.Form
{
    private BrotDemo brotDemo;
    private RectangleF zoomRect;
    private List<RectangleF> testRects;
    private Random rand = new Random();
    private int testRectCount = 10;
    private Thread worker;
    private bool shutdown = false;
    private PictureBox pictureBox1;
    private Panel panel1;
    private RadioButton radioButton3;
    private RadioButton radioButton2;
    private RadioButton radioButton1;
    private StatusStrip statusStrip1;
    private ToolStripStatusLabel toolStripStatusLabel1;
    private ToolStripStatusLabel toolStripStatusLabel2;
    private Bitmap bm;

    public BrotWindowsForm()
    {
        initialiseRect();
        InitializeComponent();

        brotDemo = new BrotDemo();
        CheckButtons();
        ClientSize = new System.Drawing.Size(512, 512);
        toolStripStatusLabel1.Text = brotDemo.DeviceName();
        bm = brotDemo.DoBrot(zoomRect);

        //initialise zoom rectangles
        float zoom = 0.90f;
        testRects = new List<RectangleF>();
        for (int i = 0; i < testRectCount; i++)
            for (int j = 0; j < testRectCount; j++)
                testRects.Add(new RectangleF(
                    new PointF((1 - zoom) / testRectCount * i, (1 - zoom) / testRectCount * j),
                    new SizeF(zoom, zoom)));

        //start calculation thread
        worker = new Thread(delegate()
        {
            while (!shutdown)
                generateAndZoom();
        });
        worker.Start();

        //legacy code 
        // Smoother display update when drawing bitmap myself in OnPaint
        //SetStyle(ControlStyles.DoubleBuffer, true);
        //SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        //SetStyle(ControlStyles.UserPaint, true);

    }

    void initialiseRect()
    {
        lock (this)
        {
            zoomRect = new RectangleF(-2f, -2f, 4f, 4f);
        }
    }

    void generateAndZoom()
    {
        bm = brotDemo.DoBrot(zoomRect);

        if (brotDemo.state != BrotDemo.State.Buddha)
        {
            //count how many pixels turned on in each sampling rectangle
            int[] score = new int[testRects.Count];
            int topScore = 0;
            for (int i = 0; i < testRectCount; i++)
                for (int j = 0; j < testRectCount; j++)
                {
                    if (bm.GetPixel(i * bm.Width / testRectCount, j * bm.Height / testRectCount).R > 0)
                        for (int r = 0; r < score.Length; r++)
                            if (testRects[r].Contains(i / (float)testRectCount, j / (float)testRectCount))
                                score[r] += 1;
                    if (testRects[0].Contains(i / (float)testRectCount, j / (float)testRectCount))
                        topScore++;
                }

            //find the rectangle with closest to half pixels turned on
            int bestScore = 0;
            RectangleF bestRect = testRects[0];
            for (int r = 0; r < score.Length; r++)
            {
                if (score[r] > topScore / 2)
                    score[r] = topScore - score[r];
                if (score[r] > bestScore || (score[r] == bestScore && rand.NextDouble() > 0.3))
                {
                    bestScore = score[r];
                    bestRect = testRects[r];
                }
            }

            //zoom into the best rectangle
            lock (this)
            {
                zoomRect.X += zoomRect.Width * bestRect.X;
                zoomRect.Y += zoomRect.Height * bestRect.Y;
                zoomRect.Width *= bestRect.Width;
                zoomRect.Height *= bestRect.Height;
            }
            //if (zoomRect.Width < 1e-7f)
            //    zoomRect = new RectangleF(-1f, -1f, 2f, 2f);

            //reset
            if (zoomRect.Width < float.MinValue)
                zoomRect = new RectangleF(-1f, -1f, 2f, 2f);
        }
        else
        {
            //brotDemo.buddha_min_iterations *= 2;
            brotDemo.buddha_max_iterations *= 2;
        }
        Invalidate();
    }

    // Override standard form behavior to display the current state
    protected override void OnPaint(PaintEventArgs e)
    {
        pictureBox1.Image = bm;
        //if (bm != null)
        //    e.Graphics.DrawImage(bm, ClientRectangle);
        toolStripStatusLabel2.Text = "frames/sec = " + 1000 / brotDemo.lasttimeMs;
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        base.OnFormClosing(e);
        shutdown = true;
        worker.Join();
    }

    public static void Main(string[] args)
    {
        BrotWindowsForm brotWindowsForm = new BrotWindowsForm();
        Application.Run(brotWindowsForm);
    }

    private void InitializeComponent()
    {
            this.pictureBox1 = new System.Windows.Forms.PictureBox();
            this.panel1 = new System.Windows.Forms.Panel();
            this.radioButton3 = new System.Windows.Forms.RadioButton();
            this.radioButton2 = new System.Windows.Forms.RadioButton();
            this.radioButton1 = new System.Windows.Forms.RadioButton();
            this.statusStrip1 = new System.Windows.Forms.StatusStrip();
            this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
            this.toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
            this.panel1.SuspendLayout();
            this.statusStrip1.SuspendLayout();
            this.SuspendLayout();
            // 
            // pictureBox1
            // 
            this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.pictureBox1.Location = new System.Drawing.Point(0, 29);
            this.pictureBox1.Name = "pictureBox1";
            this.pictureBox1.Size = new System.Drawing.Size(284, 233);
            this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pictureBox1.TabIndex = 0;
            this.pictureBox1.TabStop = false;
            // 
            // panel1
            // 
            this.panel1.Controls.Add(this.radioButton3);
            this.panel1.Controls.Add(this.radioButton2);
            this.panel1.Controls.Add(this.radioButton1);
            this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
            this.panel1.Location = new System.Drawing.Point(0, 0);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(284, 29);
            this.panel1.TabIndex = 1;
            // 
            // radioButton3
            // 
            this.radioButton3.Appearance = System.Windows.Forms.Appearance.Button;
            this.radioButton3.AutoSize = true;
            this.radioButton3.Location = new System.Drawing.Point(95, 4);
            this.radioButton3.Name = "radioButton3";
            this.radioButton3.Size = new System.Drawing.Size(54, 23);
            this.radioButton3.TabIndex = 1;
            this.radioButton3.Text = "Buddha";
            this.radioButton3.UseVisualStyleBackColor = true;
            this.radioButton3.CheckedChanged += new System.EventHandler(this.radioButton3_CheckedChanged);
            // 
            // radioButton2
            // 
            this.radioButton2.Appearance = System.Windows.Forms.Appearance.Button;
            this.radioButton2.AutoSize = true;
            this.radioButton2.Location = new System.Drawing.Point(50, 4);
            this.radioButton2.Name = "radioButton2";
            this.radioButton2.Size = new System.Drawing.Size(39, 23);
            this.radioButton2.TabIndex = 1;
            this.radioButton2.Text = "CPU";
            this.radioButton2.UseVisualStyleBackColor = true;
            this.radioButton2.CheckedChanged += new System.EventHandler(this.radioButton2_CheckedChanged);
            // 
            // radioButton1
            // 
            this.radioButton1.Appearance = System.Windows.Forms.Appearance.Button;
            this.radioButton1.AutoSize = true;
            this.radioButton1.Checked = true;
            this.radioButton1.Location = new System.Drawing.Point(4, 4);
            this.radioButton1.Name = "radioButton1";
            this.radioButton1.Size = new System.Drawing.Size(40, 23);
            this.radioButton1.TabIndex = 0;
            this.radioButton1.TabStop = true;
            this.radioButton1.Text = "GPU";
            this.radioButton1.UseVisualStyleBackColor = true;
            this.radioButton1.CheckedChanged += new System.EventHandler(this.radioButton1_CheckedChanged);
            // 
            // statusStrip1
            // 
            this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.toolStripStatusLabel1,
            this.toolStripStatusLabel2});
            this.statusStrip1.Location = new System.Drawing.Point(0, 240);
            this.statusStrip1.Name = "statusStrip1";
            this.statusStrip1.Size = new System.Drawing.Size(284, 22);
            this.statusStrip1.TabIndex = 2;
            this.statusStrip1.Text = "statusStrip1";
            // 
            // toolStripStatusLabel1
            // 
            this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
            this.toolStripStatusLabel1.Size = new System.Drawing.Size(118, 17);
            this.toolStripStatusLabel1.Text = "toolStripStatusLabel1";
            // 
            // toolStripStatusLabel2
            // 
            this.toolStripStatusLabel2.Name = "toolStripStatusLabel2";
            this.toolStripStatusLabel2.Size = new System.Drawing.Size(118, 17);
            this.toolStripStatusLabel2.Text = "toolStripStatusLabel2";
            // 
            // BrotWindowsForm
            // 
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Controls.Add(this.statusStrip1);
            this.Controls.Add(this.pictureBox1);
            this.Controls.Add(this.panel1);
            this.Name = "BrotWindowsForm";
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
            this.panel1.ResumeLayout(false);
            this.panel1.PerformLayout();
            this.statusStrip1.ResumeLayout(false);
            this.statusStrip1.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

    }

    void CheckButtons()
    {
        if(radioButton1.Checked)
            brotDemo.state = BrotDemo.State.GPU;
        else if (radioButton2.Checked)
            brotDemo.state = BrotDemo.State.CPU;
        else if (radioButton3.Checked)
            brotDemo.state = BrotDemo.State.Buddha;
        initialiseRect();
    }

    private void radioButton1_CheckedChanged(object sender, EventArgs e)
    {
        CheckButtons();
    }
    private void radioButton2_CheckedChanged(object sender, EventArgs e)
    {
        CheckButtons();
    }
    private void radioButton3_CheckedChanged(object sender, EventArgs e)
    {
        CheckButtons();
    }

}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// This class handles the application logic.
// The program initially creates a bitmap image.
// The bitmap is rendered to the display during handling of paint events.
class BrotDemo
{
    public enum State { GPU, CPU, Buddha };
    public State state = State.GPU;
    public int size;
    Stopwatch timer = new Stopwatch();
    public int max_iterations = 250;
    public long lasttimeMs;
    public int buddha_min_iterations = 1000, buddha_max_iterations = 100000;

    public BrotDemo()
    {
        size = 512;  //must be divisible TILE_SIZE
        //size = 4096;  //must be divisible TILE_SIZE
    }

    /// <summary>
    /// Functions defined in cpp
    /// </summary>
    [DllImport("amplebrotLib", CallingConvention = CallingConvention.StdCall)]
    public static extern unsafe void get_device_name([MarshalAs(UnmanagedType.LPWStr)]StringBuilder name);
    [DllImport("amplebrotLib", CallingConvention = CallingConvention.StdCall)]
    static extern unsafe void brot_amp_optimised(float* dists, int w, int h, float left, float width, float top, float height, int max_iterations, int oversample);
    [DllImport("amplebrotLib", CallingConvention = CallingConvention.StdCall)]
    static extern unsafe void brot_amp_basic(float* dists, int w, int h, float left, float width, float top, float height, int max_iterations, int oversample);
    [DllImport("amplebrotLib", CallingConvention = CallingConvention.StdCall)]
    static extern unsafe void brot_cpu_single(float* dists, int w, int h, float left, float width, float top, float height, int max_iterations, int oversample);
    [DllImport("amplebrotLib", CallingConvention = CallingConvention.StdCall)]
    static extern unsafe void brot_cpu_parallel(float* dists, int w, int h, float left, float width, float top, float height, int max_iterations, int oversample);
    [DllImport("amplebrotLib", CallingConvention = CallingConvention.StdCall)]
    static extern unsafe void buddhabrot_amp(float* dists, int w, int h, int min_iterations, int max_iterations);

    public unsafe string DeviceName()
    {
        StringBuilder name = new StringBuilder(1000);
        get_device_name(name);
        return name.ToString();
    }

    public unsafe Bitmap DoBrot(RectangleF rect)
    {
        timer.Reset();
        timer.Start();
        int w = size, h = size;
        float[] dists = new float[w * h];

        //call C++ AMP to populate the array
        fixed(float* distsPtr = &dists[0])
        {
            switch (state)
            {
                case State.GPU:
                    //brot_amp_basic(distsPtr, w, h, rect.Left, rect.Width, rect.Top, rect.Height, max_iterations, 4);
                    brot_amp_optimised(distsPtr, w, h, rect.Left, rect.Width, rect.Top, rect.Height, max_iterations, 4);
                    break;
                case State.CPU:
                    //brot_cpu_single(distsPtr, w, h, rect.Left, rect.Width, rect.Top, rect.Height, max_iterations, 4);
                    brot_cpu_parallel(distsPtr, w, h, rect.Left, rect.Width, rect.Top, rect.Height, max_iterations, 4);
                    break;
                case State.Buddha:
                    buddhabrot_amp(distsPtr, w, h, buddha_min_iterations, buddha_max_iterations);
                    break;
            }
        }

        //translate pixels to image
        float maxD = 0, minD=float.MaxValue;
        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                float d = dists[x*w + y];
                maxD = Math.Max(maxD, d);
                minD = Math.Min(minD, d);
            }
        }
        Bitmap workingBitmap = new Bitmap(size, size);
        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                //from http://en.wikipedia.org/wiki/Mandelbrot_set#Continuous_.28smooth.29_coloring
                float d = (dists[x*w + y] - minD) / (maxD-minD);
                if (maxD == minD || d == 0)
                {
                    workingBitmap.SetPixel(x, y, Color.FromArgb(0, 0, 0));
                }
                else
                {
                    int pix = (int)(128 * d);
                    workingBitmap.SetPixel(x, y, Color.FromArgb(pix, pix * 2 / 3, 255 - pix));
                }
            }
        }
        lasttimeMs = timer.ElapsedMilliseconds;
        Console.WriteLine((state==State.Buddha?buddha_max_iterations:max_iterations).ToString() + " max_iterations in " + lasttimeMs.ToString() + "ms");
        //Console.WriteLine(rect.ToString());
        return workingBitmap;
    }
}

//-----------------------------------------------------------------------------
